﻿﻿/*
*  Copyright (C) 2010 by Benjamin Nolmans <benjamin.nolmans@gmail.com>
*  and Barbara Post <postb99@gmail.com>
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using OsamesFrameworkCore.Configuration;
using OsamesFrameworkCore.Logging;
using OsamesFrameworkCore.Plugins.Management.Types;
using OsamesFrameworkCore.Plugins.Tools;
using OsamesFrameworkCore.Tools;

namespace OsamesFrameworkCore.Plugins.Management
{
    /// <summary>
    /// This class manages plugins : plugins lookup to fill an available plugins list.
    /// This lookup is called in a static method at early application life time.
    /// This class follows singleton pattern : holds a static member of its very own type,
    /// because PluginsManager is the Host of every plugin.
    /// </summary>
    public class PluginsManager : IPluginHost, ILoggable
    {
        #region attributes
        /// <summary>
        /// Singleton instance.
        /// </summary>
        private static PluginsManager _pmManager;

        /// <summary>
        /// Set to true when FindPlugins has been called.
        /// </summary>
        private static bool _bFindPluginsCalled;

        /// <summary>
        /// Lock used to instantiate the plugins manager static member in a thread-safe manner.
        /// </summary>
        private static readonly object _oLockManagerInstance = new object();

        #endregion

        #region properties
        /// <summary>
        /// Internal dictionary of AvailablePlugin objects. Dictionary key is given by 
        /// GetPluginUniqueId() of PluginsManager.
        /// </summary>
        public Dictionary<string, AvailablePlugin> DicAvailablePluginSet { get; private set; }

        /// <summary>
        /// Access to singleton.
        /// </summary>
        public static PluginsManager Instance
        {
            get
            {
                lock (_oLockManagerInstance)
                {
                    return _pmManager ?? (_pmManager = new PluginsManager());
                }
            }
        }

        #endregion

        #region methods
        /// <summary>
        /// Empty constructor is private (singleton pattern).
        /// </summary>
        private PluginsManager()
        {
            DicAvailablePluginSet = new Dictionary<string, AvailablePlugin>();
        }

        /// <summary>
        /// Logging facility.
        /// </summary>
        /// <param name="strMessage_">Message</param>
        /// <param name="strMessageDetails_">Optional array of message details. In this case, strMessage_ parameter contains placeholders, each matching a value in strMessageDetails_</param>
        /// <param name="eLogLevel_">Log level</param>
        public void Log(LogLevel eLogLevel_, string strMessage_, object[] strMessageDetails_ = null)
        {
            LoggerService.Log(this, eLogLevel_, strMessage_, strMessageDetails_);
        }

        /// <summary>
        /// Logging facility.
        /// </summary>
        /// <param name="eLogLevel_">Osames log level</param>
        /// <param name="strMessage_">Message</param>
        /// <param name="ex_">Exception we will log message and stack trace of</param>
        public void Log(LogLevel eLogLevel_, string strMessage_, Exception ex_)
        {
            LoggerService.Log(this, eLogLevel_, strMessage_, ex_);
        }

        /// <summary>
        /// Searches the appropriate directory for Plugins
        /// </summary>
        public void FindPlugins()
        {
            if (_bFindPluginsCalled)
            {
                Log(LogLevel.Info, "FindPlugins already called");
                return;
            }

            string strLookupPath = CoreConfiguration.PluginsDirectoryPath;
            if (LoggerService.IsEnabled(this, LogLevel.Trace))
            {
                string strMessage = string.Format("Going to load plugins in directory : \"{0}\"", strLookupPath);
                Log(LogLevel.Trace, strMessage);
                LoggerService.LogToOutput(strMessage);
            }

            // First empty the collection, we're reloading them all
            DicAvailablePluginSet.Clear();
            List<KeyValuePair<string, IPlugin>> lstPlugin = ReflectionTools.FindPublicConcreteClassWithPathImplementing<IPlugin>(strLookupPath);
            int iCount = lstPlugin.Count;
            if (iCount > 0)
            {
                for (int i = 0; i < iCount; i++)
                {
                    AddPlugin(lstPlugin[i].Key, lstPlugin[i].Value);
                    Log(LogLevel.Trace, "Loaded plugin : '{0}'", new object[] { lstPlugin[i].Key });
                }
            }
            else
            {
                LoggerService.Log(this, LogLevel.Warn, "No IPlugin found in directory {0}",
                                  new object[] { strLookupPath });
            }

            _bFindPluginsCalled = true;
        }

        /// <summary>
        /// Unloads and Closes all AvailablePlugins
        /// </summary>
        public void ClosePlugins()
        {
            foreach (KeyValuePair<string, AvailablePlugin> kvpApPlugin in DicAvailablePluginSet)
            {
                kvpApPlugin.Value.Plugin.Dispose();
                LoggerService.LogToOutput("Plugin " + kvpApPlugin.Key + " disposed madame!");
            }

            //Finally, clear our collection of available plugins
            DicAvailablePluginSet.Clear();
        }

        /// <summary>
        /// Add plugin
        /// </summary>
        /// <param name="strFileName_">dll name of plugin (full path)</param>
        /// <param name="iPlugin_">IPlugin instance</param>
        private void AddPlugin(string strFileName_, IPlugin iPlugin_)
        {
            // Create a new available plugin since the type implements the IPlugin interface
                        AvailablePlugin apAvailablePlugin = new AvailablePlugin
                                                                {
                                                                    Plugin = iPlugin_,
                                                                    PluginLocation = strFileName_
                                                                };

                        // Create a new instance and store the instance in the collection for later use.
                        // Every class in plugins directory that is a plugin will be loaded aas a plugin.
                        // If a plugin shouldn't be used, it must be removed from that directory.

                        // Set plugin host
                        apAvailablePlugin.Plugin.Host = this;

                        // Check configuration syntax : XML file exists and loads correctly.
                        apAvailablePlugin.LoadPluginConfig();

                        // Call the initialization sub of the plugin
                        apAvailablePlugin.Plugin.Initialize();

                        //Add the new plugin to our plugins set
                        string strUniqueId = PluginsTools.GetPluginUniqueId(apAvailablePlugin.Plugin);
                        if (DicAvailablePluginSet.ContainsKey(strUniqueId))
                        {
                            Log(LogLevel.Error, string.Format("Plugin with unique ID '{0}' already loaded", strUniqueId));
                        }
                        else
                        {

                            DicAvailablePluginSet.Add(strUniqueId, apAvailablePlugin);
                            if(LoggerService.IsEnabled(this, LogLevel.Info))
                            {
                                string strMessage = string.Format("Plugin '{0}' (type : {1}) loaded", strUniqueId,
                                                                  apAvailablePlugin.Plugin.GetType().FullName);
                                Log(LogLevel.Info, strMessage);
                                LoggerService.LogToOutput(strMessage);
                            }
                        }

                        //cleanup a bit
                        // ReSharper disable RedundantAssignment
                        apAvailablePlugin = null;
                        // ReSharper restore RedundantAssignment
                    }

        /// <summary>
        /// Find plugin by name.
        /// </summary>
        /// <param name="pluginName_">Fullname of plugin (IPlugin) type</param>
        /// <returns>AvailablePlugin instance or null</returns>
        public AvailablePlugin Find(string pluginName_)
        {
            if (DicAvailablePluginSet.ContainsKey(pluginName_))
            {
                return DicAvailablePluginSet[pluginName_];
            }
            return null;
        }

        /// <summary>
        /// Removes an available plugin from internal dictionary.
        /// </summary>
        /// <param name="pluginToRemove_"></param>
        public void Remove(AvailablePlugin pluginToRemove_)
        {
            DicAvailablePluginSet.Remove(PluginsTools.GetPluginUniqueId(pluginToRemove_.Plugin));
        }

        /// <summary>
        /// Displays a feedback dialog from the plugin
        /// </summary>
        /// <param name="feedback_">String message for feedback</param>
        /// <param name="plugin_">The plugin that called the feedback</param>
        public void Feedback(string feedback_, IPlugin plugin_)
        {
        }
        #endregion
    }
}
